﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IML;
using I = IML.Shared;
using System.Drawing;
using IML.Shared;

namespace IML
{
    public class Parser
    {
        #region Properties

        private I.ImageCompilerRequest _request { get; set; }
        private Lexer _lex { get; set; }

        #endregion

        #region Constructors

        public Parser(Lexer lex)
        {
            _lex = lex;
            _request = new I.ImageCompilerRequest()
            {
                CanvasWidth = 600,
                CanvasHeight = 400,
                InitialBrush = new I.Brush() { FillColor = ColorTranslator.FromHtml("#000000") }
            };
        }

        #endregion

        #region Public Methods

        public I.ImageCompilerRequest Parse()
        {
            START();
#if false
            throw new Exception("Parse Success");
#endif
            return _request;
        }

        #endregion

        #region Private Helper Methods

        private Token accept(params string[] args)
        {
            expecting(args);
            _lex.Accept();
            return _lex.LookBehind(1);
        }
        private Token accept(params TokenType[] args)
        {
            expecting(args);
            _lex.Accept();
            return _lex.LookBehind(1);
        }

        private void expecting(params string[] args)
        {
            KeyValuePair<bool, string> expected = isA(args);
            if (!expected.Key) error(expected.Value);
        }
        private void expecting(params TokenType[] args)
        {
            KeyValuePair<bool, string> expected = isA(args);
            if (!expected.Key) error(expected.Value);
        }

        private KeyValuePair<bool, string> isA(params string[] args)
        {
            bool isExpected = false;
            string expected = "";
            foreach (string expecting in args)
            {
                if (_lex.Current().Value.Equals(expecting)) isExpected = true;
                expected += expecting + " | ";
            }
            return new KeyValuePair<bool, string>(isExpected, expected.Trim(' ', '|'));
        }
        private KeyValuePair<bool, string> isA(params TokenType[] args)
        {
            bool isExpected = false;
            string expected = "";
            foreach (TokenType expecting in args)
            {
                if (_lex.Current().TokenType == expecting) isExpected = true;
                expected += expecting.ToString() + " | ";
            }
            return new KeyValuePair<bool, string>(isExpected, expected.Trim(' ', '|'));
        }

        private void error(string expecting)
        {
            throw new Exception(String.Format("Expecting \"{0}\" on line {1} not \"{2}\"", expecting, _lex.Current().LineNum, _lex.Current().Value.ToString()));
        }

        #endregion

        #region Private Grammar Rule Methods

        private void START()
        {
            CDEFS();
            BRUSH(_request.InitialBrush);
            accept("canvas");
            accept("(");
            _request.CanvasWidth = int.Parse(accept(TokenType.Numeric).Value);
            accept(",");
            _request.CanvasHeight = int.Parse(accept(TokenType.Numeric).Value);
            accept(")");
            SUBLIST(_request);
            END();
        }
        private void END()
        {
            if (!_lex.End()) error("EOF");
        }

        private void CDEFS()
        {
            if (isA(TokenType.Constant, TokenType.Color).Key)
            {
                CDEF();
                CDEFS();
            }
        }
        private void CDEF()
        {
            var constant = accept(TokenType.Constant, TokenType.Color).Lexem;
            accept("=");
            var ctok = accept(TokenType.Color);
            _lex.AddConstant(constant, ctok);
        }

        private void BRUSH(ShapeContainer container)
        {
            I.Brush brush = new I.Brush() { FillColor = ColorTranslator.FromHtml("#000000") };
            container.Invocations.Add(new I.BrushInvocation() { Brush = brush });
            BRUSH(brush);
        }
        private void BRUSH(I.Brush brush)
        {
            accept("brush");
            accept("(");
            brush.FillColor = ColorTranslator.FromHtml(accept(TokenType.Color).Value);
            accept(")");
        }

        private void SUBLIST(ShapeContainer container)
        {
            if (isA("{").Key)
            {
                accept("{");
                if (!STATEMENT(container)) error("STATEMENT");
                STATEMENTLIST(container);
                accept("}");
            }
        }

        private void STATEMENTLIST(ShapeContainer container)
        {
            if (STATEMENT(container))
            {
                STATEMENTLIST(container);
            }
        }

        private List<I.XYAttribute> POINTSEGMENTLIST(List<I.XYAttribute> xylist)
        {
            accept("[");
            POINT(xylist);
            SEGMENTLIST(xylist);
            accept("]");
            return xylist;
        }

        private I.PointProducerFunction COORDINATES()
        {
            List<I.XYAttribute> xylist = new List<XYAttribute>();
            I.PointProducerFunction ppf = null;
            if (isA("[").Key)
            {
                ppf = new I.PointList() { Verticies = POINTSEGMENTLIST(xylist).ToArray() };
            }
            else if (isA("ppellipses").Key)
            {
                accept("ppellipses");
                accept("(");
                I.XYAttribute center = POINT();
                accept(";");
                POINTSEGMENTLIST(xylist);
                accept(";");
                if (isA("[").Key)
                {
                    List<I.XYAttribute> coords = new List<XYAttribute>();
                    List<float> absolutePositions = new List<float>();
                    accept("[");
                    coords.Add(POINT());
                    SEGMENTLIST(coords);
                    accept("]");
                    foreach (I.XYAttribute point in coords)
                    {
                        absolutePositions.Add(point.X);
                    }
                    ppf = new I.EllipseFunction(absolutePositions)
                    {
                        Center = center,
                        Radiuses = xylist.ToArray()
                    };
                }
                else
                {

                    int numPoints = int.Parse(accept(TokenType.Numeric).Value);
                    accept(";");
                    float offset = float.Parse(accept(TokenType.Numeric).Value);
                    
                    ppf = new I.EllipseFunction(numPoints, offset)
                    {
                        Center = center,
                        Radiuses = xylist.ToArray()
                    };
                }
                accept(")");
            }
            else if (isA("ppline").Key)
            {
                accept("ppline");
                accept("(");
                I.XYAttribute start = POINT();
                accept(";");
                I.XYAttribute end = POINT();
                accept(";");
                if (isA("[").Key)
                {
                    List<I.XYAttribute> coords = new List<XYAttribute>();
                    List<float> absolutePositions = new List<float>();
                    accept("[");
                    coords.Add(POINT());
                    SEGMENTLIST(coords);
                    accept("]");
                    foreach (I.XYAttribute point in coords)
                    {
                        absolutePositions.Add(point.X);
                    }
                    ppf = new I.LineFunction(absolutePositions)
                    {
                        Start = start,
                        End = end
                    };
                }
                else
                {
                    int numPoints = int.Parse(accept(TokenType.Numeric).Value);
                    accept(";");
                    float offset = float.Parse(accept(TokenType.Numeric).Value);
                    
                    ppf = new I.LineFunction(numPoints, offset)
                    {
                        Start = start,
                        End = end
                    };
                }
                accept(")");
            }
            else error("COORDINATES");
            return ppf;
        }

        private void SEGMENTLIST(List<I.XYAttribute> xylist)
        {
            if (isA(":").Key)
            {
                accept(":");
                POINT(xylist);
                SEGMENTLIST(xylist);
            }
        }

        private void POINT(List<I.XYAttribute> xylist)
        {
            xylist.Add(POINT());
        }
        private I.XYAttribute POINT()
        {
            float x = -100;
            float y = -100;
            bool aspect = false;
            if (isA("<").Key)
            {
                aspect = true;
                accept("<");
            }
            x = float.Parse(accept(TokenType.Numeric).Value.ToString());
            if (isA(",").Key)
            {
                accept(",");
                y = float.Parse(accept(TokenType.Numeric).Value.ToString());
            }
            else if (isA(">", ":", "]", ";", ")").Key)
            {
                y = x;
                //accept(":");
            }
            else
            {
                expecting(",", ">", ":");
            }
            if (aspect) accept(">");
            return new XYAttribute() { X = x, Y = y, MaintainAspectRatio = aspect };
        }

        private bool STATEMENT(ShapeContainer container)
        {
            bool isStatement = true;
            if (isA("brush").Key) BRUSH(container); // BRUSH
            else if (isA(TokenType.ShapeVar).Key) // SHAPEVAR's
            {
                if (_lex.Next().Value.Equals("=")) SHAPEASSIGNMENT(container); // SHAPEASSIGNMENT
                else if (_lex.Next().Value.Equals("*") || _lex.LookAhead(4).Value.Equals("*"))
                {
                    SHAPEEXPRESSION(container); // SHAPEVAR * COORDINATES                 
                }
                else SHAPEVAR(container); // SHAPE
            }
            else if (isA("polygon", "ellipse").Key) // SHAPEDEF's
            {
                I.ShapeContainer shape = SHAPEDEF(container);
                if (isA("*").Key) // SHAPEDEF * COORDINATES
                {
                    throw new NotImplementedException("SHAPEDEF * COORDINATES");
                    // need to create an incremental temp name and create shape invocations as declarations
                    // followed by an invocation if not an expression
                    //accept("*");
                    //COORDINATES();
                }
            }
            else isStatement = false;

            return isStatement;
        }

        private KeyValuePair<string, int> SHAPEVAR()
        {
            int depth = 0;
            string name = accept(TokenType.ShapeVar).Value;
            if (isA("(").Key)
            {
                accept("(");
                depth = int.Parse(accept(TokenType.Numeric).Value);
                accept(")");
            }
            return new KeyValuePair<string, int>(name, depth);
        }
        private void SHAPEVAR(ShapeContainer container)
        {
            int depth = 0;
            string name = accept(TokenType.ShapeVar).Value;
            if (isA("(").Key)
            {
                accept("(");
                depth = int.Parse(accept(TokenType.Numeric).Value);
                accept(")");
            }
            container.Invocations.Add(new I.ShapeVariableInvocation() { VariableName = name, recusionDepth = depth });
        }

        private I.NestedShape SHAPEDEF()
        {
            I.NestedShape shape = null;
            if (isA("polygon").Key)
            {
                accept("polygon");
                accept("(");
                shape = new I.Polygon() { PointsFunction = COORDINATES() };
                accept(")");
            }
            else if (isA("ellipse").Key)
            {
                accept("ellipse");
                accept("(");
                var center = POINT();
                accept(";");
                var point = POINT();
                shape = new I.Ellipse() { Center = center, XYRadius = point };
                accept(")");
            }
            SUBLIST(shape);
            return shape;
        }
        private I.NestedShape SHAPEDEF(ShapeContainer container)
        {
            I.NestedShape shape = null;
            if (isA("polygon").Key)
            {
                accept("polygon");
                accept("(");
                shape = new I.Polygon() { PointsFunction = COORDINATES() };
                container.Invocations.Add(new I.ShapeInvocation() { Shape = shape });
                accept(")");
            }
            else if (isA("ellipse").Key)
            {
                accept("ellipse");
                accept("(");
                var center = POINT();
                accept(";");
                var point = POINT();
                shape = new I.Ellipse() { Center = center, XYRadius = point };
                container.Invocations.Add(new I.ShapeInvocation() { Shape = shape });
                accept(")");
            }
            SUBLIST(shape);
            return shape;
        }

        private void SHAPEEXPRESSION(ShapeContainer container)
        {
            var shapeVar = SHAPEVAR();
            accept("*");
            container.Invocations.Add(new I.Repeater()
            {
                VariableName = shapeVar.Key,
                recusionDepth = shapeVar.Value,
                PointFunction = COORDINATES()
            });
        }

        private void SHAPEASSIGNMENT(ShapeContainer container)
        {
            string varName = accept(TokenType.ShapeVar).Value;
            accept("=");
            container.ShapeDeclarations.Add(
                new KeyValuePair<string, I.NestedShape>(varName, SHAPEDEF()));
        }

        #endregion
    }
}
